package org.maxkey.authn.support.jwt;

import java.util.Date;
import java.util.UUID;

import javax.servlet.http.HttpServletResponse;

import org.joda.time.DateTime;
import org.maxkey.config.ApplicationConfig;
import org.maxkey.config.oidc.OIDCProviderMetadataDetails;
import org.maxkey.constants.LOGINTYPE;
import org.maxkey.crypto.jwt.signer.service.impl.DefaultJwtSigningAndValidationService;
import org.maxkey.web.WebContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.PlainJWT;
import com.nimbusds.jwt.SignedJWT;

public class JwtLoginService {
	private static final Logger _logger = LoggerFactory
			.getLogger(JwtLoginService.class);

	protected ApplicationConfig applicationConfig;

	OIDCProviderMetadataDetails jwtProviderMetadata;

	DefaultJwtSigningAndValidationService jwtSignerValidationService;

	public boolean login(String jwt, HttpServletResponse response) {
		_logger.debug("jwt : " + jwt);

		String username = null;
		SignedJWT signedJWT = null;

		boolean loginResult = false;
		JWTClaimsSet  jwtClaimsSet = null;
		try {

			RSASSAVerifier rsaSSAVerifier = new RSASSAVerifier(
					((RSAKey) jwtSignerValidationService.getAllPublicKeys()
							.get(jwtSignerValidationService
									.getDefaultSignerKeyId())).toRSAPublicKey());

			signedJWT = SignedJWT.parse(jwt);
			if (signedJWT.verify(rsaSSAVerifier)) {
				loginResult = true;
			} else {
				_logger.debug("verify false ");
				return false;
			}
			jwtClaimsSet = signedJWT.getJWTClaimsSet();

			_logger.debug("" + signedJWT.getPayload());
			_logger.debug("jwtClaimsSet Issuer " + jwtClaimsSet.getIssuer());
			_logger.debug("Metadata Issuer " + jwtProviderMetadata.getIssuer());

			if (loginResult
					&& jwtClaimsSet.getIssuer().equals(
							jwtProviderMetadata.getIssuer())) {
				loginResult = true;
				_logger.debug("Issuer equals ");
			} else {
				_logger.debug("Issuer not equals ");
				return false;
			}

			_logger.debug("username " + jwtClaimsSet.getSubject());

			if (loginResult && jwtClaimsSet.getSubject() != null) {
				username = jwtClaimsSet.getSubject();
			} else {
				return false;
			}

			DateTime now = new DateTime();

			if (loginResult
					&& now.isBefore(jwtClaimsSet.getExpirationTime().getTime())) {
				if (WebContext.setAuthentication(username, LOGINTYPE.JWT, "",
						"", "success")) {
					return true;
				}
			}
		} catch (java.text.ParseException e) {
			// Invalid signed JWT encoding
			_logger.error("Invalid signed JWT encoding ");
		} catch (JOSEException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			_logger.error("JOSEException ");
		} 

		return false;
	}

	public String buildLoginJwt() {
		_logger.debug("buildLoginJwt .");
		
		DateTime currentDateTime = DateTime.now();
		Date expirationTime = currentDateTime.plusMinutes(5).toDate();
		_logger.debug("expiration Time : " + expirationTime);
		JWTClaimsSet jwtClaims =  new JWTClaimsSet.Builder()
				      .subject(WebContext.getSession().getId())
				      .expirationTime(expirationTime)
				      .issuer(jwtProviderMetadata.getIssuer())
				      .issueTime(currentDateTime.toDate())
				      .jwtID(UUID.randomUUID().toString())
				      .build();
		
		JWT jwtToken = new PlainJWT(jwtClaims);

		_logger.info("jwt Claims : " + jwtClaims.toString());

		JWSAlgorithm signingAlg = jwtSignerValidationService
				.getDefaultSigningAlgorithm();

		jwtToken = new SignedJWT(new JWSHeader(signingAlg), jwtClaims);
		// sign it with the server's key
		jwtSignerValidationService.signJwt((SignedJWT) jwtToken);

		String tokenString = jwtToken.serialize();
		_logger.debug("jwt Token : " + tokenString);
		return tokenString;
	}

	public boolean jwtTokenValidation(String jwt) {
		SignedJWT signedJWT = null;

		boolean loginResult = false;
		JWTClaimsSet jwtClaimsSet = null;
		try {

			RSASSAVerifier rsaSSAVerifier = new RSASSAVerifier(
					((RSAKey) jwtSignerValidationService.getAllPublicKeys()
							.get(jwtSignerValidationService
									.getDefaultSignerKeyId())).toRSAPublicKey());

			signedJWT = SignedJWT.parse(jwt);
			if (signedJWT.verify(rsaSSAVerifier)) {
				loginResult = true;
			} else {
				_logger.debug("verify false ");
			}
			jwtClaimsSet = signedJWT.getJWTClaimsSet();

			_logger.debug("" + signedJWT.getPayload());

			_logger.debug("username " + jwtClaimsSet.getSubject());
			
			_logger.debug("jwtClaimsSet Issuer " + jwtClaimsSet.getIssuer());
			_logger.debug("Metadata Issuer " + jwtProviderMetadata.getIssuer());

			if (loginResult
					&& jwtClaimsSet.getIssuer().equals(
							jwtProviderMetadata.getIssuer())) {
				loginResult = true;
				_logger.debug("Issuer equals ");
			} else {
				_logger.debug("Issuer not equals ");
				return false;
			}

			DateTime now = new DateTime();

			if (loginResult
					&& now.isBefore(jwtClaimsSet.getExpirationTime().getTime())) {
				_logger.debug("ExpirationTime  Validation " + now.isBefore(jwtClaimsSet.getExpirationTime().getTime()));
				loginResult = true;
			}else{
				return false;
			}
		} catch (java.text.ParseException e) {
			// Invalid signed JWT encoding
			_logger.debug("Invalid signed JWT encoding ");
		} catch (JOSEException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			_logger.debug("JOSEException ");
		} 
		return loginResult;
	}

	public void setApplicationConfig(ApplicationConfig applicationConfig) {
		this.applicationConfig = applicationConfig;
	}

	public void setJwtProviderMetadata(
			OIDCProviderMetadataDetails jwtProviderMetadata) {
		this.jwtProviderMetadata = jwtProviderMetadata;
	}

	public void setJwtSignerValidationService(
			DefaultJwtSigningAndValidationService jwtSignerValidationService) {
		this.jwtSignerValidationService = jwtSignerValidationService;
	}

}
